import edu.mit.jwi.*;
import edu.mit.jwi.item.IIndexWord;
import edu.mit.jwi.item.POS;
import edu.mit.jwi.item.ISynset;
import edu.mit.jwi.item.ISynsetID;
import edu.mit.jwi.item.IWord;
import edu.mit.jwi.item.IWordID;
import edu.mit.jwi.item.Pointer;
import edu.mit.jwi.morph.WordnetStemmer;

import java.io.IOException;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import java.util.List;
import java.util.Iterator;

public class WNDictionary {
	
	/** The dictionary_directory. */
	private static String dictionary_directory = "dict";
	
	/** The instance. */
	private static WNDictionary instance;
	
	/** The dictionary_. */
	IDictionary dictionary_;
	
	/** The stemmer_. */
	WordnetStemmer stemmer_;
	
	/** The available_pos. */
	POS []available_pos = {POS.ADVERB, POS.ADJECTIVE, POS.VERB, POS.NOUN};
	
	/**
	 * The Class IWordIterator.
	 * This class is used for iterating over set of word sequences in WordNet.
	 */
	class IWordIterator	implements Iterator<IWord> {
		
		/** The word_list_. */
		ArrayList<IWord> word_list_;
		
		/** The word_. */
		String word_;
		
		/** The dictionary_. */
		IDictionary dictionary_;
		
		/** The curr_pos_. */
		int curr_pos_;
		
		/**
		 * Instantiates a new IWordIterator.
		 * 
		 * @param dictionary the dictionary
		 * @param word the word
		 */
		public IWordIterator(IDictionary dictionary, String word) {
			this.word_ = word;
			this.dictionary_ = dictionary;
			this.curr_pos_ = 0;
			this.word_list_ = new ArrayList<IWord>();

			IIndexWord idxWord;
			for (POS each_pos : available_pos) {
				idxWord = dictionary_.getIndexWord(word, each_pos);
				if (idxWord != null) {
					for ( IWordID each_word_idx : idxWord.getWordIDs())
						word_list_.add(dictionary_.getWord(each_word_idx));
				}
			}
		}
		
		/* (non-Javadoc)
		 * @see java.util.Iterator#remove()
		 */
		public void remove() {
			word_list_.remove(--curr_pos_);
		}
		
		/* (non-Javadoc)
		 * @see java.util.Iterator#hasNext()
		 */
		public boolean hasNext() {
			return (curr_pos_ < word_list_.size());
		}
		
		/* (non-Javadoc)
		 * @see java.util.Iterator#next()
		 */
		public IWord next() {
			return word_list_.get(curr_pos_++);
		}
		
	}
	
	/**
	 * Gets the single instance of WNDictionary.
	 * 
	 * @return single instance of WNDictionary
	 */
	public static synchronized WNDictionary getInstance() {
		if (instance == null) {
			try {
				instance = new WNDictionary();
			} catch (IOException e) {
				instance = null;
			}
		}
		return instance;
	}
	
	/**
	 * Instantiates a new wN dictionary.
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public WNDictionary() throws IOException {
		URL dict_url = null;
		boolean error_occured = false;
		
		try {
 			dict_url = new URL("file", null, (new File(Util.getDataPathOf(dictionary_directory))).getPath());
		} catch (MalformedURLException e) {
			error_occured = true;
		}
		
		if (!error_occured) {
			dictionary_ = new Dictionary(dict_url);
			error_occured = !dictionary_.open();
		}
		
		if (error_occured)
			throw new IOException("Unable to initlaize dictionary!");
		else
			stemmer_ = new WordnetStemmer(dictionary_);
	}
	
	/**
	 * get a tagged word instance and returns set of stem words.
	 * e.g. likes >> like, convincing >> convince
	 * 
	 * @param tagged_word the tagged_word
	 * 
	 * @return List<String>
	 */
	public List<String> getBaseWords(TaggedWordExt tagged_word) {
		POS pos = translatePOS(tagged_word);
		List<String> stems;
		
		if (pos == null)
			stems = stemmer_.findStems(tagged_word.getWord());
		else
			stems = stemmer_.findStems(tagged_word.getWord(), pos);
		
		return stems;
	}
	
	/**
	 * Gets the at least one base (stem) words.
	 * If there isn't any stem word, then return a word itself.
	 * 
	 * @param tagged_word the tagged_word
	 * 
	 * @return the at least one base words
	 */
	public List<String> getAtLeastOneBaseWords(TaggedWordExt tagged_word) {
		List<String> result = getBaseWords(tagged_word);
		if (result.size() == 0) {
			result = new ArrayList<String>();
			result.add(tagged_word.getWord());
		}
		return result;
	}
		
	/**
	 * Check whether a given word exists in the WordNet dictionary
	 * With the given POS tag.
	 * 
	 * @param word the word
	 * @param pos the pos
	 * 
	 * @return true, if successful
	 */
	public boolean doesExist(String word, POS pos) {
		if(pos == null) return doesExist(word);
		
		IIndexWord idxWord = dictionary_.getIndexWord(word, pos);
		return (idxWord != null);
	}
	
	/**
	 * Checks whether a given word exists in the WordNet dictionary.
	 * (Doesn't care about POS!)
	 * 
	 * @param word the word
	 * 
	 * @return true, if successful
	 */
	public boolean doesExist(String word) {
		IIndexWord idxWord;
		for (POS each_pos : available_pos) {
			idxWord = dictionary_.getIndexWord(word, each_pos);
			if (idxWord != null)
				return true;
		}
		return false;
	}
	
	/**
	 * Gets the list of pointer.
	 * 
	 * @param word the word
	 * @param point the point
	 * 
	 * @return the list of pointer
	 */
	private Set<String> getListOfPointer(String word, Pointer point) {
		IWordIterator iter = new IWordIterator(dictionary_, word);
		HashMap<String, Boolean> synonymList = new HashMap<String, Boolean>();
		IWord currWord;
		ISynset currSynset;
		List<ISynsetID> synonyms;
				
		while (iter.hasNext()) {
			currWord = iter.next();
			currSynset = currWord.getSynset();
			synonyms = currSynset.getRelatedSynsets(point);
			for (ISynsetID eachSynsetID : synonyms)
				for (IWord eachWord : dictionary_.getSynset(eachSynsetID).getWords())
					synonymList.put(eachWord.getLemma(), true);
		}
		
		return synonymList.keySet();
	}
		
	
	/**
	 * Gets the list of synonyms.
	 * 
	 * @param word the word
	 * 
	 * @return the list of synonyms
	 */
	public Set<String> getListOfSynonyms(String word) {
		return getListOfPointer(word, Pointer.SIMILAR_TO);
	}
	
	/**
	 * Gets the list of antonyms.
	 * 
	 * @param word the word
	 * 
	 * @return the list of antonyms
	 */
	public Set<String> getListOfAntonyms(String word) {
		return getListOfPointer(word, Pointer.ANTONYM);
	}
	
	/**
	 * Translate TaggedWordExt specific POS into General POS tag.
	 * 
	 * @param word the word
	 * 
	 * @return the pOS
	 */
	public static POS translatePOS(TaggedWordExt word) {
		if (word.isAdjective()) {
			return POS.ADJECTIVE;
		} else if (word.isAdverb()) {
			return POS.ADVERB;
		} else if (word.isVerb() || word.isModal()) {
			return POS.VERB;
		} else if (word.isNoun()) {
			return POS.NOUN;
		}
		return null;
	}
}